package Implementacion;

import java.util.HashSet;
import java.util.Set;

import model.Pieza;
import model.Tablero;
import model.Variante;
import Interfaces.Ficha;
import Interfaces.IJuego;
import TDA.ConjuntoTDA;

public class Juego implements IJuego {

	public static final int DIMENSION_DEFAULT = 6;
	
	final Tablero tablero;
	long movimientos;
	
	/**
	 * Crea el juego con un tablero que responde a la dimensión recibida 
	 * por parámetro.
	 * @param size del tablero a construir.
	 */
	public Juego(final int size) {
		tablero = new Tablero(size);
	}

	/**
	 * Crea el juego con un tablero de dimensión default.
	 */
	public Juego() {
		this(DIMENSION_DEFAULT);
	}

	/**
	 * Método principal del juego. Busca generar las piezas con todas las 
	 * variantes posibles desde las fichas dadas, para luego invocar el 
	 * método que realiza la operatoria de backtracking que llena el tablero.
	 * @param fichas generadas para insertar.
	 * @return true si pudo resolverlo, false si no se logró el objetivo. 
	 */
	@Override
	public boolean rompecabeza2D(ConjuntoTDA<Ficha> fichas) {
		final Set<Pieza> piezas = generarPiezasDesde(fichas);
		movimientos = 0;
		boolean armado = puzzle(tablero, piezas);		
		return armado;		
	}

	/**
	 * Genera las piezas desde el conjunto de fichas base dadas.
	 * @param fichas para generar piezas, las que generan las variantes.
	 * @return el conjunto de piezas generadas.
	 */
	private Set<Pieza> generarPiezasDesde(final ConjuntoTDA<Ficha> fichas) {
		final Set<Pieza> piezas = new HashSet<Pieza>();
		Ficha elegida = null; 
		while (!fichas.conjuntoVacio()) {
			elegida = fichas.elegir();
			piezas.add(new Pieza(elegida));
			fichas.sacar(elegida);
		}
		return piezas;
	}

	/**
	 * Método principal de la operatoria de solución. <br>
	 * Implementa un algoritmo básico de backtracking, sin mayores 
	 * optimizaciones, que busca insertar todas las piezas en el tablero
	 * utilizando las variantes generadas hasta encontrar la solución,
	 * o en su defecto salir por ser imposible.
	 * @param tablero
	 * @param piezas
	 * @return
	 */
	private boolean puzzle(final Tablero tablero, final Set<Pieza> piezas) {
		// Para cada pieza disponible,
		for (Pieza pieza : piezas) {
			// para cada variante de la misma,
			for (Variante variante : pieza.getVariantes()) {
				// se la intenta insertar en el tablero.
				final boolean piezaFueInsertada = tablero.insertar(variante); 
				// Si fue insertada,
				if (piezaFueInsertada) {
					movimientos++;
					// Si el tablero está lleno
					if (tablero.estaLleno()) {
						// se entiende que se encontró una solución.
						return true;
					// En caso de que todavía se lo pueda llenar,
					} else {
						final Set<Pieza> piezasRestantes = 
								new HashSet<Pieza>(piezas);
						piezasRestantes.remove(pieza);
						// Recursivamente seguimos con las piezas que quedan.
						boolean haySolucion = 
								puzzle(tablero, piezasRestantes);
						// Si al retornar hubo solución, se notifica.
						if (haySolucion) {
							return true;
						// Si no la hubo, esta variante de la pieza no sirve,
						} else {
							// por lo que se remueve para continuar con otra.
							tablero.removerUltima();
						}
					}
				}
				// Si esta variante de la pieza no fue insertada,
				// hay que seguir probando las que quedan.
			}
			// Si ninguna variante logró llegar a una solución, 
			// simplemente se sigue con las otras piezas.
		}
		// Si se llega a probar todas las piezas sin llegar a una solución, 
		// se notifica que no se pudo resolver con este conjunto de piezas.
		return false;
	}

	/**
	 * Se deberá mostrar por pantalla cómo queda cubierto el tablero, de acuerdo
	 * a cómo fueron las piezas organizadas. En cada casilla del tablero deberá
	 * ir la identificación de la ficha "Nombre", que cubrió la misma.
	 */
	public void imprimirTablero() {
		// El tablero en sí mismo debe saber representarse como un string, 
		// el cual usamos para imprimir en salida estándar.
		System.out.println(tablero);
		System.out.println("Cantidad de movimientos realizados: " + movimientos);
	}
	
}
